<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title></title>
		<style type="text/css">
			.all{
				text-align: center;
				border: 2px solid #000000;
				display:inline-block;
			}
			#ban{
				display: flex;
				margin: 0 auto;
			}
			#canvas{
				display: inline;
			}
			#panel{
				display: inline;
				border: 1px solid #000000;
				display: grid;
				grid-template-columns: repeat(2, 50%);
				/* grid-template-rows: repeat(9, 11.1%); */
				grid-template-rows: repeat(auto-fill, 1rf);
			}
			#download{
				text-decoration: none;
				color: #000000;
				width: 100%;
				height: 100%;
				display: flex;
				align-items: center;
				justify-content: center;
			}
			.line div{
				width: 60%;
				height: 7%;
				margin: 0 auto;
				background-color: #000000;
			}
			.circle div{
				border: 1px solid #000000;
				border-radius: 50%;
				width: 60%;
				height: 60%;
				margin: 0 auto;
			}
			.rect div{
				border: 1px solid #000000;
				width: 60%;
				height: 30%;
				margin: 0 auto;
			}
			.square div{
				border: 1px solid #000000;
				width: 50%;
				height: 50%;
				margin: 0 auto;
			}
			.qu div{
				width: 60%;
				height: 30%;
				border: 1px solid #000000;
				border-bottom: none;
				border-radius: 54px 54px 0 0;
				margin: 0 auto;
			}
			.eraser div,.free div{
				width: 70%;
				height: 30%;
				margin: 0 auto;
				background-color: #FFFFFF;
				border: 1px solid #000000;
				display: flex;
				justify-content: center;
				align-items: center;
				box-shadow: 0 0 3px 0px #a3a3a3;
			}
			.size,.color{
				grid-column-start: 1;
				grid-column-end: 3;
			}
			.color input,.size input{
				width: 60%;
				height: 30%;
			}
			.ellipse div{
				border: 1px solid #000000;
				border-radius: 50%;
				width: 60%;
				height: 30%;
				margin: 0 auto;
			}
			.active{
				background:linear-gradient(to left top,#ff1a5f,#b727ff,#1ca8ff,#22ff7a,#d6ff20);
			}
		</style>
	</head>
	<body>
		<div class="all">
			<div id="ban">
				<canvas id="canvas" width="900" height="600"></canvas>
				<div id="panel">
					<button type="button" class="line">
						<div></div>
					</button>
					<button type="button" class="qu">
						<div></div>
					</button>
					<button type="button" class="circle">
						<div></div>
					</button>
					<button type="button" class="ellipse">
						<div></div>
					</button>
					<button type="button" class="square">
						<div></div>
					</button>
					<button type="button" class="rect">
						<div></div>
					</button>
					<button type="button" class="free">
						<div>画笔</div>
					</button>
					<button type="button" class="eraser">
						<div>橡皮擦</div>
					</button>
					<button type="button" class="back">
						后退
					</button>
					<button type="button" class="forward">
						前进
					</button>
					<button type="button" class="clearAll">
						清空
					</button>
					<button type="button" class="restoreAll">
						恢复
					</button>
					<button type="button">
						<a href="" download="download" id='download'>下载</a>
					</button>
					<button type="button" class="fill">
						描边
					</button>
					<button type="button" class="color">
						<input type="color"/>
					</button>
					<button type="button" class="size">
						<input type="range"/>
					</button>
				</div>
			</div>
		</div>
		<script type="text/javascript">
			// 类的this指向，可能需要箭头函数控制
			// 没事多closePath
			class Ban{
				constructor(canvas=document.querySelectorAll('#ban #canvas')[0],width=1000,height=700) {
					this.width=width
					this.height=height
					this.c=canvas
					this.c.width=width
					this.c.height=height
					this.pen=this.c.getContext('2d')
					this.penWidth=5
					this.penColor='black'
					this.type='line'
					this.mode='stroke'
					// 曲线是两个步骤(1.绘制参考直线;2.消除直线并改为曲线)，此处判断是否是绘制曲线的第二步
					this.quChange=false
					this.beginX=0
					this.beginY=0
					// 保存上一次绘制时的开始坐标
					this.quBeginX=0
					this.quBeginY=0
					this.endX=0
					this.endY=0
					// 保存上一次绘制时的结束坐标
					this.quEndX=0
					this.quEndY=0
					this.moveX=0
					this.moveX=0
					this.panelDom=document.querySelectorAll('#ban #panel')[0]
					// 自适应网格布局
					this.panelDom.style.height=this.c.offsetHeight+'px'
					this.panelDom.style.width=this.c.offsetHeight/9*2+'px'
					// 初始化下载链接
					this.downloadDom=document.querySelectorAll('#panel #download')[0]
					this.downloadDom.href=this.c.toDataURL()
					// 用于前进后退的保存数据
					this.data=[this.pen.getImageData(0,0,900,600)]
					this.index=0
					this.initEvent()
				}
				
				initEvent(){
					this.initMouseDraw()
					this.chooseType()
					this.isFill()
				}
				
				// 鼠标绘制事件
				initMouseDraw(){
					// 用于防止点击操作台mousedown时，同时触发本函数最后一行的事件
					let protectEnd=false
					document.addEventListener('mouseup',(e)=>{
						if(!protectEnd){
							this.end(e)
						}
						protectEnd=false
					},false)
					this.panelDom.addEventListener('mousedown',e=>{
						protectEnd=true
					},false)
					this.c.addEventListener('mousedown',(e)=>{this.begin(e)},false)
				}
				
				// 选择功能
				chooseType(){
					for(let i=0;i<this.panelDom.children.length;i++){
						this.panelDom.children[i].onclick=(e)=>{
							// 各种模式
							if(i<8){
								this.type=this.panelDom.children[i].className
								for(let i of this.panelDom.children){
									i.style=''
								}
								this.panelDom.children[i].style='background:linear-gradient(to left top,#ff1a5f,#b727ff,#1ca8ff,#22ff7a,#d6ff20);'
							}
								// 前进后退
							if(i>7&&i<12){
								this[this.panelDom.children[i].className]();
							}
						}
					}
				}
				
				
				isFill(){
					let that=this
					// 选择填充还是描边
					document.querySelectorAll('#panel .fill')[0].onclick=function(){
						if(this.innerText=='描边'){
							this.innerText='填充'
							this.classList.add('active')
							that.mode='fill'
						}
						else if(this.innerText=='填充'){
							this.innerText='描边'
							this.classList.remove('active')
							that.mode='stroke'
						}
					}
				}
				
				initUrl(){
					this.downloadDom.href=this.c.toDataURL()
				}
				
				getColor(){
					this.penColor=document.querySelectorAll('#ban #panel .color input')[0].value
					
				}
				getSize(){
					this.penWidth=document.querySelectorAll('#ban #panel .size input')[0].value/10
				}
				
				begin(e){
					this.getColor()
					this.getSize()
					this.beginX=e.offsetX
					this.beginY=e.offsetY
					// 开始绘制时，清除数组中当前索引后面的记录
					// this.data=this.data.slice(0,this.index+1)
					this.data.splice(this.index+1,this.data.length-this.index)
					// 若使用曲线模式，保存第一次绘制参考直线的开始坐标
					if(this.type=='qu'&&!this.quChange){
						this.quBeginX=this.beginX
						this.quBeginY=this.beginY
					}
					this.c.onmousemove=(e)=>{
						this.move(e)
					}
				}
				
				move(e){
					this.pen.beginPath()
					this.endX=e.offsetX
					this.endY=e.offsetY
					this.moveX=this.endX-this.beginX
					this.moveY=this.endY-this.beginY
					this.makeShape()
					this.pen.closePath()
				}
				
				end(e){
					// 曲线功能下，保存第一次绘制参考直线的开始坐标并更改曲线模式
					if(this.type=='qu'){
						if(!this.quChange){
							this.quEndX=this.endX
							this.quEndy=this.endY
							this.quChange=!this.quChange
							this.c.onmousemove=()=>{}
							return
						}
						this.quChange=!this.quChange
					}
					else this.quChange=false
					
					// 每画完一笔，把数组末尾附上当前值保存
					this.saveData()
					
					this.c.onmousemove=()=>{}
				}
				
				saveData(){
					this.index++
					this.data[this.index]=this.pen.getImageData(0,0,900,600)
				}
				
				clear(){
					this.pen.clearRect(1,1,this.c.offsetWidth-2,this.c.offsetHeight-2)
				}
				
				// 索引清零，画布清零
				clearAll(){
					this.index=0
					this.pen.putImageData(this.data[0],0,0,0,0,1900,1600)
				}
				
				restore(){
					this.pen.putImageData(this.data[this.index],0,0,0,0,1900,1600)
				}
				
				// 索引、画布更到最新
				restoreAll(){
					this.index=this.data.length-1
					this.pen.putImageData(this.data[this.data.length-1],0,0,0,0,1900,1600)
				}
				
				back(){
					if(this.index<1)
						return
					this.clear()
					this.index--
					this.pen.putImageData(this.data[this.index],0,0,0,0,1900,1600)
				}
				
				forward(){
					if(this.index==this.data.length-1)
						return
					this.clear()
					this.index++
					this.pen.putImageData(this.data[this.index],0,0,0,0,1900,1600)
				}
				
				makeShape(){
					this.pen.lineWidth=this.penWidth
					this.pen.strokeStyle=this.pen.fillStyle=this.penColor
					this.pen.beginPath()
					// 画笔和橡皮擦不用边清除边画
					if(this.type=='eraser'||this.type=='free'){
						this[this.type]()
					}
					// 描边功能下，边清除边画(鼠标拖动时该函数不断调用，不一直清除的话，会跟填充一样而不会描边)
					else{
						this.clear()
						this[this.type]()
						this.restore()
						if(this.mode=='fill')
							this.pen.fill()
						this.pen.stroke()
					}
					this.pen.closePath()
				}
				
				rect(){
					this.pen.rect(this.beginX,this.beginY,this.moveX,this.moveY)
				}
				
				square(){
					let bian=Math.max(Math.abs(this.moveX),Math.abs(this.moveY)),bianX=bian,bianY=bian
					if(this.moveX<0){
						bianX=-bian
					}
					if(this.moveY<0){
						bianY=-bian
					}
					this.pen.rect(this.beginX,this.beginY,bianX,bianY)
				}
				
				circle(){
					let x=this.beginX+this.moveX/2,y=this.beginY+this.moveY/2
					this.pen.arc(x,y,Math.abs(this.moveX/2),0,2*Math.PI)
				}
				
				free(){
					this.pen.arc(this.endX,this.endY,this.penWidth/2,0,2*Math.PI)
					this.pen.fill()
				}
				
				line(){
					this.pen.moveTo(this.beginX,this.beginY)
					this.pen.lineTo(this.endX,this.endY)
				}
				
				ellipse(){
					let x=this.beginX+this.moveX/2,y=this.beginY+this.moveY/2
					this.pen.ellipse(x,y,Math.abs(this.moveX/2),Math.abs(this.moveY/2),0,0,Math.PI*2);
				}
				
				eraser(){
					this.pen.fillStyle='#fff'
					this.free()
				}
				
				qu(){
					if(this.quChange){
						this.pen.moveTo(this.quBeginX,this.quBeginY)
						this.pen.quadraticCurveTo(this.endX,this.endY,this.quEndX,this.quEndy);
					}
					else{
						this.line()
					}
				}
				
			}
			window.onload=()=>{
				new Ban()
			}
		</script>
	</body>
</html>
